package cn.coder.toolkit;


import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;

/**
 * @author com.mrathena on 2019/5/27 11:17
 */
public final class IdKit {

	private static final String EMPTY = "";
	private static final String MINUS = "-";

	public static void main(String[] args) {
		System.out.println(uuid());
		System.out.println(no());
		System.out.println(id());
		System.out.println(SnowflakeIdGenerator.next());
		System.out.println(SnowflakeIdGenerator.getSequence(SnowflakeIdGenerator.next()));
	}

	private IdKit() {}

	private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");

	private static final LocalDateTime START = LocalDateTime.of(2025, 1, 1, 0, 0, 0, 0);
	private static final long EPOCH = START.toInstant(ZoneOffset.ofHours(8)).toEpochMilli() * 1000 * 1000 / 100; // 转成百纳秒(只能精确到百纳秒级)

	public static String uuid() {
		return UUID.randomUUID().toString().replace(MINUS, EMPTY);
	}

	/**
	 * 生成32位字符串长度的只有数字的String流水号
	 */
	public static String no() {
		// 纳秒不能从LocalDateTime或Instant等获取,因为有长时间不变更的问题
		// 如连续执行 LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssnnnnnnnnn"))
		// 可能有一长串的值都是相同的, 只有毫秒在变, 微秒和纳秒都不变
		// 获取毫秒
		String milli = LocalDateTime.now().format(FORMATTER);
		// 获取微秒纳秒(精确值只能到百纳秒级,因为十纳秒和个纳秒永远都是0)
		// 这里采用毫秒拼接纳秒的做法有一点点问题, 两者跨毫秒不一致, 存在类似时钟回拨的现象
		// 不过从总体上来看, 能保证毫秒级的有序, 和百纳秒级的不重复, 已经可以用了(测下来怎么都不会重复)
		// 年月日    时分秒  毫秒 微秒纳秒
		// 20230816 164521 576 343600
		// 20230816 164521 577 431900
		// 20230816 164521 577 566500
		// 20230816 164521 577 674500
		// 20230816 164521 577 793700
		// 20230816 164521 577 934900
		// 20230816 164521 577 081500 这里出现了跨毫秒(System.nanoTime())
		// 20230816 164521 577 192500
		// 20230816 164521 577 308000
		// 20230816 164521 578 380600 这里出现了跨毫秒(LocalDateTime)
		String temp = String.valueOf(System.nanoTime());
		String nano = temp.substring(temp.length() - 6);
		// 既然纳秒的最后两位一定是0, 那就截掉, 把随机数补充进来
		String random = String.valueOf(ThreadLocalRandom.current().nextLong(10_000_000_000L, 99_999_999_999L));
		return milli + nano.substring(0, nano.length() - 2) + random;
	}

	/**
	 * 生成64位的Long型id
	 */
	public static long id() {
		// 一个 long 有 64 位, 按照如下长度分隔
		// 时间位, 54 位, 最多可以表示 2^54 个百纳秒, 约 57 年, 从 20230101 000000 起, 能表示到 2080 年, 足够用了
		// System.out.println(Math.pow(2, 54) / 10 / 1000 / 1000 / 60 / 60 / 24 / 365); // 57.12328294483126
		// 随机位, 10 位, 取值范围是 0~1023
		// 毫秒时间戳
		long milli = LocalDateTime.now().toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
		// 百纳秒时间戳
		String nanoStr = String.valueOf(System.nanoTime());
		String nano = nanoStr.substring(nanoStr.length() - 6);
		// 同样存在类似时钟回拨的现象
		long temp = (milli * 1000 * 1000 + Long.parseLong(nano)) / 100;
		// 计算百纳秒时间戳差值
		long differ = temp - EPOCH;
		// 获取随机10位数
		int random = ThreadLocalRandom.current().nextInt(0, 1 << 10);
		// 将两个部分组合在一起
		return (differ << 10) | random;
	}

	/**
	 * JVM级别雪花算法简易实现
	 * 设计思路
	 * 一个 long 有 64 位, 按照如下长度分隔
	 * 时间位, 42 位, 和雪花算法一样, 最多可以表示 2^41 个毫秒, 约 69.73 年, 从 20250101 000000 起, 足够用了
	 * 机器位, 10 位, 可表示 2^10 台机器, 即 1024 台
	 * 序号位, 12 位, 可表示 2^12 个数, 即每台机器每毫秒最多可生产 4096 个 id, 满了后会死循环到下一个毫秒再生产
	 * 根据该规则, 可以将各部分反解出来
	 */
	public static final class SnowflakeIdGenerator {

		// 起始的时间戳, 可以根据实际需求进行调整, 以 2023-01-01 00:00:00 为纪元时间, 作为本工具的起始时间点
		// System.out.println(Math.pow(2, 41) / 1000 / 60 / 60 / 24 / 365); // 69.73057000101473
//		private static final LocalDateTime EPOCH = LocalDateTime.of(2025, 1, 1, 0, 0, 0, 0);
		private static final LocalDateTime START = LocalDateTime.of(2025, 1, 1, 0, 0, 0, 0);
		private static final long EPOCH = START.atZone(ZoneOffset.ofHours(8)).toInstant().toEpochMilli(); // 转成毫秒

		// 每部分占用的位数(除最前面的时间戳部分外)
		private static final long WORKER_ID_BITS = 10L;
		private static final long SEQUENCE_BITS = 12L;

		// 每部分的最大值
		private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS); // 1023, 按位取反, 如5(0000 0101), 则~5就是-6(1111 1010), -1<<10=-1024, ~-1024=1023
		private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS); // 4095, 二进制:11 1111 1111, 与操作相当于取余

		// 每部分向左的位移, 左移是为了给其他部分腾出空间(时间戳需要左移机器位和序号位的长度, 机器位需要左移序号位的长度)
		private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;
		private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;

		// 工作机器 ID
		private final long workerId;
		// 序列号
		private long sequence = 0L;
		// 上次生成 ID 的时间戳
		private long lastTimestamp = -1L;

		public SnowflakeIdGenerator(long workerId) {
			if (workerId < 0 || workerId > MAX_WORKER_ID) {
				throw new RuntimeException(String.format("节点标识范围: [0,%d]", MAX_WORKER_ID));
			}
			this.workerId = workerId;
		}

		private static class Holder {
			private static final SnowflakeIdGenerator INSTANCE = new SnowflakeIdGenerator(1023);
		}

		private static SnowflakeIdGenerator getInstance() {
			return Holder.INSTANCE;
		}

		public static long next() {
			return SnowflakeIdGenerator.getInstance().generate();
		}

		public static long getTimestamp(long id) {
			return (id >> TIMESTAMP_SHIFT) + EPOCH;
		}

		public static LocalDateTime getLocalDateTime(long id) {
			return LocalDateTime.ofInstant(Instant.ofEpochMilli(getTimestamp(id)), ZoneId.systemDefault());
		}

		public static long getWorkerId(long id) {
			return (id >> WORKER_ID_SHIFT) & MAX_WORKER_ID;
		}

		public static long getSequence(long id) {
			return id & MAX_SEQUENCE;
		}

		public synchronized long generate() {
			// 获取当前时间戳
			long timestamp = System.currentTimeMillis();
			if (timestamp < lastTimestamp) {
				throw new RuntimeException("时钟回拨异常");
			}
			// 判断当前请求是否和上次请求在同一个毫秒内
			if (timestamp == lastTimestamp) {
				// 将序列号加 1, 并通过与 MAX_SEQUENCE 进行按位与运算来确保序列号不超过最大值
				// MAX_SEQUENCE 是一个由序列位数决定的二进制掩码。
				// 按位与运算的结果是将序列号限制在 0 到 MAX_SEQUENCE 之间，当序列号达到 MAX_SEQUENCE 时，按位与运算的结果将是 0，从而使序列号重新从 0 开始递增。
				sequence = (sequence + 1) & MAX_SEQUENCE;
				if (sequence == 0) {
					// 当前毫秒内的序列号已经用完，等待下一毫秒
					timestamp = tilNextMillis(lastTimestamp);
				}
			} else {
				// 不同毫秒内，序列号重置为 0
				sequence = 0L;
			}
			// 缓存本次时间戳
			lastTimestamp = timestamp;
			// 生成最终的 ID, 将3个部分组合起来
			return ((timestamp - EPOCH) << TIMESTAMP_SHIFT) | (workerId << WORKER_ID_SHIFT) | sequence;
		}

		private long tilNextMillis(long lastTimestamp) {
			long timestamp = System.currentTimeMillis();
			while (timestamp <= lastTimestamp) {
				timestamp = System.currentTimeMillis();
			}
			return timestamp;
		}

	}

}
